//! Parameter validation utilities

use std::collections::HashMap;
use crate::types::{ParameterSchema, ParameterValues, ValidationResult, ValidationError, ParameterType};
use regex::Regex;

/// Validates parameters against their schemas
pub fn validate_parameters(
    parameters: &ParameterValues,
    schemas: &HashMap<String, ParameterSchema>,
) -> ValidationResult {
    let mut errors = Vec::new();

    // Check required parameters
    for (name, schema) in schemas {
        if schema.required.unwrap_or(false) {
            if !parameters.contains_key(name) {
                errors.push(ValidationError::new(
                    name,
                    "Required parameter is missing",
                ));
                continue;
            }
        }

        // Validate parameter if present
        if let Some(value) = parameters.get(name) {
            if let Err(error) = validate_parameter_value(name, value, schema) {
                errors.push(error);
            }
        }
    }

    // Check for unknown parameters
    for name in parameters.keys() {
        if !schemas.contains_key(name) {
            errors.push(ValidationError::new(
                name,
                "Unknown parameter",
            ));
        }
    }

    if errors.is_empty() {
        ValidationResult::success()
    } else {
        ValidationResult::failure(errors)
    }
}

/// Validates a single parameter value against its schema
pub fn validate_parameter_value(
    name: &str,
    value: &serde_json::Value,
    schema: &ParameterSchema,
) -> Result<(), ValidationError> {
    // Check type
    match (&schema.param_type, value) {
        (ParameterType::String, serde_json::Value::String(s)) => {
            validate_string_constraints(name, s, schema)?;
        }
        (ParameterType::Number, serde_json::Value::Number(n)) => {
            validate_number_constraints(name, n, schema)?;
        }
        (ParameterType::Boolean, serde_json::Value::Bool(_)) => {
            // Boolean values are always valid
        }
        (ParameterType::Array, serde_json::Value::Array(arr)) => {
            validate_array_constraints(name, arr, schema)?;
        }
        (ParameterType::Object, serde_json::Value::Object(_)) => {
            // Object validation could be more complex, but for now just check type
        }
        _ => {
            return Err(ValidationError::with_value(
                name,
                format!("Expected type {:?}, got {:?}", schema.param_type, get_value_type(value)),
                value.clone(),
            ));
        }
    }

    // Check enum constraints
    if let Some(enum_values) = &schema.r#enum {
        if !enum_values.contains(value) {
            return Err(ValidationError::with_value(
                name,
                format!("Value must be one of: {:?}", enum_values),
                value.clone(),
            ));
        }
    }

    Ok(())
}

/// Validates string constraints
fn validate_string_constraints(
    name: &str,
    value: &str,
    schema: &ParameterSchema,
) -> Result<(), ValidationError> {
    // Check pattern
    if let Some(pattern) = &schema.pattern {
        let regex = Regex::new(pattern).map_err(|_| {
            ValidationError::new(name, "Invalid regex pattern in schema")
        })?;
        
        if !regex.is_match(value) {
            return Err(ValidationError::with_value(
                name,
                format!("String does not match pattern: {}", pattern),
                serde_json::Value::String(value.to_string()),
            ));
        }
    }

    // Check length constraints (using min/max as length bounds for strings)
    if let Some(min) = schema.min {
        if (value.len() as f64) < min {
            return Err(ValidationError::with_value(
                name,
                format!("String length must be at least {}", min),
                serde_json::Value::String(value.to_string()),
            ));
        }
    }

    if let Some(max) = schema.max {
        if (value.len() as f64) > max {
            return Err(ValidationError::with_value(
                name,
                format!("String length must be at most {}", max),
                serde_json::Value::String(value.to_string()),
            ));
        }
    }

    Ok(())
}

/// Validates number constraints
fn validate_number_constraints(
    name: &str,
    value: &serde_json::Number,
    schema: &ParameterSchema,
) -> Result<(), ValidationError> {
    let num_value = value.as_f64().unwrap_or(0.0);

    if let Some(min) = schema.min {
        if num_value < min {
            return Err(ValidationError::with_value(
                name,
                format!("Number must be at least {}", min),
                serde_json::Value::Number(value.clone()),
            ));
        }
    }

    if let Some(max) = schema.max {
        if num_value > max {
            return Err(ValidationError::with_value(
                name,
                format!("Number must be at most {}", max),
                serde_json::Value::Number(value.clone()),
            ));
        }
    }

    Ok(())
}

/// Validates array constraints
fn validate_array_constraints(
    name: &str,
    value: &[serde_json::Value],
    schema: &ParameterSchema,
) -> Result<(), ValidationError> {
    // Check length constraints
    if let Some(min) = schema.min {
        if (value.len() as f64) < min {
            return Err(ValidationError::with_value(
                name,
                format!("Array length must be at least {}", min),
                serde_json::Value::Array(value.to_vec()),
            ));
        }
    }

    if let Some(max) = schema.max {
        if (value.len() as f64) > max {
            return Err(ValidationError::with_value(
                name,
                format!("Array length must be at most {}", max),
                serde_json::Value::Array(value.to_vec()),
            ));
        }
    }

    // Validate items if schema is provided
    if let Some(items_schema) = &schema.items {
        for (index, item) in value.iter().enumerate() {
            let item_name = format!("{}[{}]", name, index);
            if let Err(error) = validate_parameter_value(&item_name, item, items_schema) {
                return Err(error);
            }
        }
    }

    Ok(())
}

/// Gets the type of a JSON value
fn get_value_type(value: &serde_json::Value) -> &'static str {
    match value {
        serde_json::Value::Null => "null",
        serde_json::Value::Bool(_) => "boolean",
        serde_json::Value::Number(_) => "number",
        serde_json::Value::String(_) => "string",
        serde_json::Value::Array(_) => "array",
        serde_json::Value::Object(_) => "object",
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use serde_json::json;

    #[test]
    fn test_validate_required_parameter() {
        let mut schemas = HashMap::new();
        schemas.insert("test".to_string(), ParameterSchema {
            param_type: ParameterType::String,
            required: Some(true),
            default: None,
            description: None,
            r#enum: None,
            min: None,
            max: None,
            pattern: None,
            items: None,
            properties: None,
        });

        let parameters = HashMap::new();
        let result = validate_parameters(&parameters, &schemas);
        
        assert!(!result.valid);
        assert_eq!(result.errors.len(), 1);
        assert_eq!(result.errors[0].path, "test");
    }

    #[test]
    fn test_validate_string_parameter() {
        let mut schemas = HashMap::new();
        schemas.insert("test".to_string(), ParameterSchema {
            param_type: ParameterType::String,
            required: Some(true),
            default: None,
            description: None,
            r#enum: None,
            min: Some(3.0),
            max: Some(10.0),
            pattern: None,
            items: None,
            properties: None,
        });

        let mut parameters = HashMap::new();
        parameters.insert("test".to_string(), json!("hello"));
        
        let result = validate_parameters(&parameters, &schemas);
        assert!(result.valid);
    }
}
